#!/usr/bin/env python
# -*- coding: iso8859 -*-

"""
	ReadArgs - By Fabio Rotondo ( fabio@rotondo.it )
"""

__revision__ = 1

import re

class ReadArgs ( object ):
	"""
		ReadArgs
		========

		This class parses string accordingly to the ReadArgs's syntax defined by Amiga OS in 1992.
		
	"""
	# {{{ __init__ ()
	def __init__ ( self ):
		# Public attributes
		self.result = None

		self.strict_mode = 1

		# Private attributes
		self.__template = None
		self.__text = None
		self.__template_data = None

		# Regular Expressions used to parse Template and Text
		self.__template_regex = re.compile ( r"[, ]*(?P<cmd>[a-zA-Z][a-zA-Z0-9_/]*[/SNAKMF]*)(?P<help>\{[^}]*\})*" )
		self.__text_regex = re.compile ( r' *(?P<arg>([A-Za-z][A-Z0-9_]*=*)*("[^"]*"|[a-zA-Z0-9_-]+))' )
		self.__named_arg_regex = re.compile ( r'(?P<name>[a-zA-Z][a-zA-Z0-9_]*)="?(?P<val>[^"]*)"?' )
	# }}}

	# ======================================================================================
	# PUBLIC METHODS
	# ======================================================================================
	# {{{ set_template ( tmpl )
	def set_template ( self, tmpl ):
		"""
			set_template ( tmpl )

			Use this method to set ReadArgs parsing template. Template should follow the following syntax:

				- Every parameter in the template should be separated by a "," or a " "
				- Every parameter can have some "switches" that will change the behaviour for ReadArgs.
				  Each swicth must be preceded by a "/" and should not contain any space.
				  The list of the supported swicthes is the following:

					+ A		- This parameter is MANDATORY (ABSOLUTE)
					+ N		- The parameter value is a number (integer)
		"""
		# Create a new Template list
		self.__template = []

		self.__split_template ( tmpl )

		# Gathers some data about the current template
		# to speed up text parsing later
		self.__gather_template_data ()
	# }}}
	# {{{ parse ( txt )
	def parse ( self, txt ):
		"""
			parse ( txt )

			This method gets in input the string ``txt`` and parses it accordingly to the
			currently set template (use ``set_template()`` to set it).
		"""

		self.result = {}

		if not self.__template: 
			# ERROR: no template given
			return 0

		# Text must be splitted on spaces, but
		# keeping in mind that some values can be
		# set using ""
		self.__split_txt ( txt )

		# Now it is time to start the "real" parsing
		# 1. Fill all the "named" value
		self.__fill_names ()

		# 2. Fill switches
		self.__fill_switches ()

		# 3. Fill by pos
		self.__fill_by_pos ()
	# }}}
	# {{{ get ( param_name, defval = None )
	def get ( self, param_name, defval = None ):
		"""
			get ( param_name, defval = None )
		"""
		return self.result.get ( param_name.lower (), defval )
	# }}}

	# ======================================================================================
	# PRIVATE METHODS
	# ======================================================================================
	# {{{ __split_template ( template_string )
	def __split_template ( self, s ):
		print "--- ", s
		m = self.__template_regex.match ( s )

		while m:
			if m.group ( "help" ):
				next_pos = m.end ( "help" )
				hlp = m.group ( "help" )
			else:
				next_pos = m.end ( "cmd" )
				hlp = ''

			self.__add_template ( m.group ( "cmd" ), hlp )

			s = s [ next_pos :  ]
			m = self.__template_regex.match ( s )
		
		print self.__template
	# }}}
	# {{{ __add_template ( tok, help )
	def __add_template ( self, tok, hlp ):
		field = { "name" : '',
			  "keyword" : 0,
			  "required" : 0,
			  "numeric" : 0,
			  "float" : 0,
			  "switch" : 0,
			  "multi" : 0,
			  "help" : ''
			}

		if hlp: 
			field [ 'help' ] = hlp [ 1: -1 ]

		data = tok.split ( "/" )
		
		# The first item is the field name
		field [ 'name' ] = data [ 0 ].lower ()

		# If the field contains some switches
		if len ( data ) > 1:
			for swt in data [ 1: ]:
				swt = swt.upper ()
				if   swt == 'A':  
					field [ 'required' ] = 1
				elif swt == 'K':  
					field [ 'keyword' ] = 1
				elif swt == 'N':  
					field [ 'numeric' ] = 1
				elif swt == 'F':  
					field [ 'float' ] = 1
				elif swt == 'S':  
					field [ 'switch' ] = 1
				elif swt == 'M':  
					field [ 'multi' ] = 1

		self.__template.append ( field )
	# }}}
	# {{{ __gather_template_data ( self )
	def __gather_template_data ( self ):
		self.__template_data =  { 
						"multi" : None,
						"keywords" : {},
						"fields" : {},
						"switches" : {},
					}

		for field in self.__template:
			if field [ 'multi' ]: 	
				self.__template_data [ 'multi' ] = field [ 'name' ]
			elif field [ 'keyword' ]:
				self.__template_data [ 'keywords' ] [ field [ 'name' ] ] = field
			elif field [ 'switch' ]:   
				self.__template_data [ 'switches' ] [ field [ 'name' ] ] = field

			self.__template_data [ 'fields' ] [ field [ 'name' ] ] = field
	# }}}
	# {{{ __split_txt ( s )
	def __split_txt ( self, s ):
		self.__text = []

		m = self.__text_regex.match ( s )
		pos = 0
		while m:
			txt = m.group ( "arg" )
			if txt [ 0 ] == '"': 
				txt = txt [ 1 : -1 ]
			self.__text.append ( txt )
			pos = m.end ( "arg" )
			m = self.__text_regex.match ( s, pos )

		print self.__text
	# }}}
	# {{{ __fill_names ()
	def __fill_names ( self ):
		pos_to_del = []
		pos = -1 
		for w in self.__text:
			pos += 1

			m = self.__named_arg_regex.match ( w )

			if not m: 
				continue

			# Save the pos to be deleted from the self.__text
			pos_to_del.insert ( 0, pos )

			name = m.group ( "name" ).lower ()

			if name not in self.__template_data [ 'fields' ]:
				print "ERROR: %s argument is not valid" % name
	
			self.__set_param_val ( name, m.group ( "val" ) )


		for p in pos_to_del: 
			del self.__text [ p ]
	# }}}
	# {{{ __fill_switches ()
	def __fill_switches ( self ):
		pos_to_del = []
		pos = -1 
		for w in self.__text:
			pos += 1

			w = w.lower ()

			if w not in self.__template_data [ 'switches' ]: 
				continue

			pos_to_del.insert ( 0, pos )
			self.result [ w ] = 1

		for p in pos_to_del: 
			del self.__text [ p ]
	# }}}
	# {{{ __fill_by_pos ()
	def __fill_by_pos ( self ):
		for txt in self.__text:
			pname = self.__get_free_param ()
			self.__set_param_val ( pname, txt )
	# }}}
	# {{{ __get_free_param ()
	def __get_free_param ( self ):
		"""
			__get_free_param ( self ) -> string

			This function returns the first param in the template that
			does not have a value yet.

			If all params already have values, returns the "multi" param (if any).
		"""

		# First try to return required fields 
		for tmpl in self.__template:
			if tmpl [ "switch" ] == 0 and tmpl [ "name" ] not in self.result and tmpl [ "required" ]: 
				return tmpl [ "name" ]

		# Then try to return any empty field that should not be keyword-specified
		for tmpl in self.__template:
			if tmpl [ "switch" ] == 0 and tmpl [ "keyword" ] == 0 and tmpl [ "name" ] not in self.result:
				return tmpl [ "name" ]

		# Then, if ``strict_mode`` is not set, try to get any parameter still empty
		if not self.strict_mode:
			for tmpl in self.__template:
				if tmpl [ "switch" ] == 0 and tmpl [ "name" ] not in self.result:
					return tmpl [ "name" ]
		

		if self.__template_data [ 'multi' ]: 
			return self.__template_data [ 'multi' ]
	
		print "ERROR: too many params for template"

		return "error"
	# }}}
	# {{{ __convert_val ( field, v )
	def __convert_val ( self, field, v ):
		if field [ 'numeric' ]: 
			v = int ( v )
		elif field [ 'float' ]: 
			v = float ( v )
		elif field [ 'switch' ]: 
			if v in ( 'on', 'ON', '1', 1 ): 
				v = 1
			else: 
				v = 0
	
		return v
	# }}}
	# {{{ __set_param_val ( name, v )
	def __set_param_val ( self, name, v ):
		field = self.__template_data [ 'fields' ] [ name ]
	
		v = self.__convert_val ( field, v )

		if field [ 'multi' ]: 
			if self.result.get ( name ): 
				self.result [ name ].append ( v )
			else: 
				self.result [ name ] = [ v ]
		else:
			self.result [ name ] = v
	# }}}

if __name__ == '__main__':
	ra = ReadArgs ()
	ra.set_template ( "NAME/A{The user name}, SURNAME/A{The user surname}, ADMIN/S, AGE/N, GAMES/K/M" )

	def do_test ( s ):
		"""
			a small test function"
		"""
		print "======= %s" % s
		ra.parse ( s )
		print "+++++++ %s" % ra.result

	do_test ( "Fabio Rotondo admin" )
	do_test ( 'Gino Latino admin=0' )
	do_test ( "SURNAME=Rotondo age=24 Fabio" )
	do_test ( "SURNAME=Rotondo age=24 Fabio game1 game2 game3" )

#
# vim60: foldmethod=marker ts=8
